﻿#region copyright info
//------------------------------------------------------------------------------
// <copyright company="ChaosStudio">
//     Copyright (c) 2002-2010 巧思工作室.  All rights reserved.
//     Contact:		MSN:zhouyu@cszi.com , QQ:478779122
//		Link:				http://www.69sea.com http://www.cszi.com
// </copyright>
//------------------------------------------------------------------------------
#endregion
using System;
using System.Collections.Generic;
using System.Data;

namespace CS.Caching
{
    /// <summary>
    ///   LRU(最近最少使用)数据缓存类。
    ///   v:0.1.0
    /// </summary>
    /// 
    /// <description class = "CS.Cache.LRUDataCache">
    ///   
    /// </description>
    /// 
    /// <history>
    ///   2010-3-22 11:18:38 , zhouyu ,  创建	     
    ///  </history>
    public class LRUDataCache
    {
        // Fields
        protected LinkedList<DataRow> AgeLink;
        protected Dictionary<DataRow, CacheDictValue> CacheDict;
        protected DataTable CacheTable;
        protected Dictionary<string, DataView> CacheViewDict;
        protected int _capacity;
        protected object[] _cols;
        protected ExpireModeType ExpireMode;
        protected object[] Keys;
        protected LinkedList<DataRow> LRULink;
        protected LinkedList<SampleQueValue> SampleQueue;
        protected int _sampleRate;
        protected double Ttl;
        public object SyncRoot;

        // Events
        public event EventHandler<HasErrorEventArgs> HasErrorEvent;

        // Methods
        public LRUDataCache(DataTable dataTableForClone, int capacity)
        {
            _capacity = -1;
            Ttl = -1.0;
            ExpireMode = ExpireModeType.Passive;
            _sampleRate = 0x2710;
            SampleQueue = new LinkedList<SampleQueValue>();
            SyncRoot = new object();
            CacheTable = dataTableForClone.Clone();
            Keys = new object[this.CacheTable.PrimaryKey.Length];
            _cols = new object[this.CacheTable.Columns.Count];
            _capacity = (capacity < 0) ? -1 : capacity;
            CacheDict = new Dictionary<DataRow, CacheDictValue>();
            LRULink = new LinkedList<DataRow>();
            AgeLink = new LinkedList<DataRow>();
            CacheViewDict = new Dictionary<string, DataView>();
        }

        public LRUDataCache(DataTable dataTableForClone, int capacity, double lifetime)
            : this(dataTableForClone, capacity)
        {
            Ttl = (lifetime < 0.0) ? -1.0 : lifetime;
        }

        public LRUDataCache(DataTable dataTableForClone, int capacity, double lifetime, ExpireModeType expireMode)
            : this(dataTableForClone, capacity, lifetime)
        {
            ExpireMode = expireMode;
        }

        public virtual void Clear()
        {
            CacheTable.Clear();
            CacheDict.Clear();
            LRULink.Clear();
            AgeLink.Clear();
            CacheViewDict.Clear();
        }

        public void GetHitRate(out int hits, out int total)
        {
            hits = SampleQueue.Last.Value.Hits;
            total = SampleQueue.Count;
        }

        protected void HitsInc()
        {
            SampleQueValue value2;
            if (SampleQueue.Count < _sampleRate)
            {
                if (SampleQueue.Count == 0)
                {
                    value2 = new SampleQueValue(1, true);
                    SampleQueue.AddLast(value2);
                }
                else
                {
                    value2 = new SampleQueValue(SampleQueue.Last.Value.Hits + 1, true);
                    SampleQueue.AddLast(value2);
                }
            }
            else
            {
                value2 = SampleQueue.First.Value.IsHit ? new SampleQueValue(SampleQueue.Last.Value.Hits, true) : new SampleQueValue(SampleQueue.Last.Value.Hits + 1, true);

                SampleQueue.RemoveFirst();
                SampleQueue.AddLast(value2);
            }
        }

        public virtual bool IsContainsIndex(string indexName)
        {
            return CacheViewDict.ContainsKey(indexName);
        }

        protected bool IsExpired(CacheDictValue cacheDictValue, DateTime nowTime)
        {
            return ((Ttl >= 0.0) && (cacheDictValue.CreateTime.AddSeconds(Ttl) <= nowTime));
        }

        protected void MissesInc()
        {
            SampleQueValue value2;
            if (SampleQueue.Count < _sampleRate)
            {
                if (SampleQueue.Count == 0)
                {
                    value2 = new SampleQueValue(0, false);
                    SampleQueue.AddLast(value2);
                }
                else
                {
                    value2 = new SampleQueValue(SampleQueue.Last.Value.Hits, false);
                    SampleQueue.AddLast(value2);
                }
            }
            else
            {
                value2 = SampleQueue.First.Value.IsHit ? new SampleQueValue(SampleQueue.Last.Value.Hits - 1, false) : new SampleQueValue(SampleQueue.Last.Value.Hits, false);

                SampleQueue.RemoveFirst();
                SampleQueue.AddLast(value2);
            }
        }

        private void OnHasError(HasErrorEventArgs e)
        {
            var hasErrorEvent = HasErrorEvent;
            if (hasErrorEvent != null)
            {
                hasErrorEvent(this, e);
            }
        }

        public virtual bool Put(DataRow dataRow)
        {
            int num;
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            if (dataRow.Table == CacheTable)
            {
                var value2 = CacheDict[dataRow];
                LRULink.Remove(value2.LruLinkNode);
                AgeLink.Remove(value2.AgeLinkNode);
                value2.UpdateCreateTime();
                LRULink.AddFirst(value2.LruLinkNode);
                AgeLink.AddFirst(value2.AgeLinkNode);
                return false;
            }
            for (num = 0; num < CacheTable.PrimaryKey.Length; num++)
            {
                Keys[num] = dataRow[CacheTable.PrimaryKey[num].ColumnName];
            }
            for (num = 0; num < CacheTable.Columns.Count; num++)
            {
                _cols[num] = dataRow[CacheTable.Columns[num].ColumnName];
            }
            return PutInternal(_cols, Keys);
        }

        public virtual bool Put(object[] itemArray, object[] keyArray)
        {
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            return PutInternal(itemArray, keyArray);
        }

        public virtual void PutIndex(string indexName, string sort)
        {
            var view = new DataView(CacheTable) {Sort = sort};
            CacheViewDict[indexName] = view;
        }

        protected virtual bool PutInternal(object[] itemArray, object[] keyArray)
        {
            CacheDictValue value2;
            OutOfMemoryException exception;
            LinkedListNode<DataRow> node;
            LinkedListNode<DataRow> node2;
            
            Label_02CD:
            var flag2 = true;
            var row = CacheTable.Rows.Find(keyArray);
            if (row != null)
            {
                value2 = CacheDict[row];
                LRULink.Remove(value2.LruLinkNode);
                AgeLink.Remove(value2.AgeLinkNode);
                row.ItemArray = itemArray;
                value2.UpdateCreateTime();
                LRULink.AddFirst(value2.LruLinkNode);
                AgeLink.AddFirst(value2.AgeLinkNode);
                return false;
            }
            if (_capacity >= 0)
            {
                while ((CacheDict.Count > 0) && (CacheDict.Count >= _capacity))
                {
                    RemoveLRU();
                }
            }
            try
            {
                row = CacheTable.Rows.Add(itemArray);
            }
            catch (OutOfMemoryException exception1)
            {
                exception = exception1;
                OnHasError(new HasErrorEventArgs(exception, CacheDict.Count));
                if (!RemoveLRU())
                {
                    throw;
                }
                _capacity = CacheDict.Count + 1;
                goto Label_02CD;
            }
            try
            {
                node = LRULink.AddFirst(row);
            }
            catch (OutOfMemoryException exception2)
            {
                exception = exception2;
                OnHasError(new HasErrorEventArgs(exception, CacheDict.Count));
                row = null;
                if (!RemoveLRU())
                {
                    throw;
                }
                _capacity = CacheDict.Count + 1;
                goto Label_02CD;
            }
            try
            {
                node2 = AgeLink.AddFirst(row);
            }
            catch (OutOfMemoryException exception3)
            {
                exception = exception3;
                OnHasError(new HasErrorEventArgs(exception, CacheDict.Count));
                row = null;
                node = null;
                LRULink.RemoveFirst();
                if (!RemoveLRU())
                {
                    throw;
                }
                _capacity = CacheDict.Count + 1;
                goto Label_02CD;
            }
            try
            {
                value2 = new CacheDictValue(node, node2);
            }
            catch (OutOfMemoryException exception4)
            {
                exception = exception4;
                OnHasError(new HasErrorEventArgs(exception, CacheDict.Count));
                row = null;
                node = null;
                LRULink.RemoveFirst();
                node2 = null;
                AgeLink.RemoveFirst();
                if (!RemoveLRU())
                {
                    throw;
                }
                _capacity = CacheDict.Count + 1;
                goto Label_02CD;
            }
            try
            {
                CacheDict.Add(row, value2);
            }
            catch (OutOfMemoryException exception5)
            {
                exception = exception5;
                OnHasError(new HasErrorEventArgs(exception, CacheDict.Count));
                row = null;
                node = null;
                LRULink.RemoveFirst();
                node2 = null;
                AgeLink.RemoveFirst();
                value2 = null;
                if (!RemoveLRU())
                {
                    throw;
                }
                _capacity = CacheDict.Count + 1;
                goto Label_02CD;
            }
            return true;
        }

        public virtual bool Remove(object[] keys)
        {
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            var row = CacheTable.Rows.Find(keys);
            if (row == null)
            {
                return false;
            }
            var value2 = CacheDict[row];
            CacheTable.Rows.Remove(row);
            LRULink.Remove(value2.LruLinkNode);
            AgeLink.Remove(value2.AgeLinkNode);
            CacheDict.Remove(row);
            return true;
        }

        protected virtual void RemoveAllExpired()
        {
            var now = DateTime.Now;
            if (Ttl >= 0.0)
            {
                var last = AgeLink.Last;
                while (last != null)
                {
                    var cacheDictValue = CacheDict[last.Value];
                    if (!IsExpired(cacheDictValue, now))
                    {
                        break;
                    }
                    CacheTable.Rows.Remove(last.Value);
                    CacheDict.Remove(last.Value);
                    LRULink.Remove(cacheDictValue.LruLinkNode);
                    AgeLink.Remove(cacheDictValue.AgeLinkNode);
                    last = last.Previous;
                }
            }
        }

        public virtual bool RemoveIndex(string indexName)
        {
            return CacheViewDict.Remove(indexName);
        }

        protected bool RemoveLRU()
        {
            DataRow row;
            return RemoveLRU(out row);
        }

        protected virtual bool RemoveLRU(out DataRow dataRow)
        {
            dataRow = null;
            var last = LRULink.Last;
            if (last == null)
            {
                return false;
            }
            dataRow = last.Value;
            var value2 = CacheDict[dataRow];
            CacheTable.Rows.Remove(dataRow);
            CacheDict.Remove(dataRow);
            LRULink.RemoveLast();
            AgeLink.Remove(value2.AgeLinkNode);
            return true;
        }

        public virtual bool RemoveSelected(string filterExpression)
        {
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            var rowArray = CacheTable.Select(filterExpression, "");
            if (rowArray.Length == 0)
            {
                return false;
            }
            for (int i = 0; i < rowArray.Length; i++)
            {
                var row = rowArray[i];
                var value2 = CacheDict[row];
                CacheTable.Rows.Remove(row);
                LRULink.Remove(value2.LruLinkNode);
                AgeLink.Remove(value2.AgeLinkNode);
                CacheDict.Remove(row);
            }
            return true;
        }

        public virtual bool TryGet(object[] keys, out DataRow dataRow)
        {
            dataRow = null;
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            var row = CacheTable.Rows.Find(keys);
            if (row == null)
            {
                MissesInc();
                return false;
            }
            var cacheDictValue = CacheDict[row];
            if (IsExpired(cacheDictValue, DateTime.Now))
            {
                CacheTable.Rows.Remove(row);
                CacheDict.Remove(row);
                LRULink.Remove(cacheDictValue.LruLinkNode);
                AgeLink.Remove(cacheDictValue.AgeLinkNode);
                MissesInc();
                return false;
            }
            cacheDictValue.UpdateAccessTime();
            LRULink.Remove(cacheDictValue.LruLinkNode);
            LRULink.AddFirst(cacheDictValue.LruLinkNode);
            HitsInc();
            dataRow = CacheTable.NewRow();
            dataRow.ItemArray = row.ItemArray;
            return true;
        }

        public virtual bool TryGet(string indexName, object[] sortKeys, out List<DataRow> listDataRow)
        {
            DataView view;
            listDataRow = null;
            if (!CacheViewDict.TryGetValue(indexName, out view))
            {
                return false;
            }
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            var viewArray = view.FindRows(sortKeys);
            if (viewArray.Length == 0)
            {
                MissesInc();
                return false;
            }
            listDataRow = new List<DataRow>();
            for (var i = 0; i < viewArray.Length; i++)
            {
                var row = viewArray[i].Row;
                var cacheDictValue = CacheDict[row];
                if (IsExpired(cacheDictValue, DateTime.Now))
                {
                    CacheTable.Rows.Remove(row);
                    CacheDict.Remove(row);
                    LRULink.Remove(cacheDictValue.LruLinkNode);
                    AgeLink.Remove(cacheDictValue.AgeLinkNode);
                }
                else
                {
                    cacheDictValue.UpdateAccessTime();
                    LRULink.Remove(cacheDictValue.LruLinkNode);
                    LRULink.AddFirst(cacheDictValue.LruLinkNode);
                    var item = CacheTable.NewRow();
                    item.ItemArray = row.ItemArray;
                    listDataRow.Add(item);
                }
            }
            if (listDataRow.Count == 0)
            {
                MissesInc();
                return false;
            }
            HitsInc();
            return true;
        }

        public virtual bool TryGet(string indexName, object[] sortKeys, out DataRow dataRow)
        {
            DataView view;
            dataRow = null;
            if (!CacheViewDict.TryGetValue(indexName, out view))
            {
                return false;
            }
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            var num = view.Find(sortKeys);
            if (num == -1)
            {
                MissesInc();
                return false;
            }
            var row = view[num].Row;
            var cacheDictValue = CacheDict[row];
            if (IsExpired(cacheDictValue, DateTime.Now))
            {
                CacheTable.Rows.Remove(row);
                CacheDict.Remove(row);
                LRULink.Remove(cacheDictValue.LruLinkNode);
                AgeLink.Remove(cacheDictValue.AgeLinkNode);
                MissesInc();
                return false;
            }
            cacheDictValue.UpdateAccessTime();
            LRULink.Remove(cacheDictValue.LruLinkNode);
            LRULink.AddFirst(cacheDictValue.LruLinkNode);
            HitsInc();
            dataRow = CacheTable.NewRow();
            dataRow.ItemArray = row.ItemArray;
            return true;
        }

        public virtual bool TrySelect(string filterExpression, string sort, out List<DataRow> listDataRow)
        {
            listDataRow = null;
            if (ExpireMode == ExpireModeType.Active)
            {
                RemoveAllExpired();
            }
            var rowArray = CacheTable.Select(filterExpression, sort);
            if (rowArray.Length == 0)
            {
                MissesInc();
                return false;
            }
            listDataRow = new List<DataRow>();
            for (var i = 0; i < rowArray.Length; i++)
            {
                var row = rowArray[i];
                var cacheDictValue = CacheDict[row];
                if (IsExpired(cacheDictValue, DateTime.Now))
                {
                    CacheTable.Rows.Remove(row);
                    CacheDict.Remove(row);
                    LRULink.Remove(cacheDictValue.LruLinkNode);
                    AgeLink.Remove(cacheDictValue.AgeLinkNode);
                }
                else
                {
                    cacheDictValue.UpdateAccessTime();
                    LRULink.Remove(cacheDictValue.LruLinkNode);
                    LRULink.AddFirst(cacheDictValue.LruLinkNode);
                    var item = CacheTable.NewRow();
                    item.ItemArray = row.ItemArray;
                    listDataRow.Add(item);
                }
            }
            if (listDataRow.Count == 0)
            {
                MissesInc();
                return false;
            }
            HitsInc();
            return true;
        }

        /// <summary>
        /// 缓存容量
        /// </summary>
        public int Capacity
        {
            get { return _capacity; }
            set
            {
                _capacity = (value < 0) ? -1 : value;
                while (CacheDict.Count > _capacity)
                {
                    RemoveLRU();
                }
            }
        }

        /// <summary>
        /// 缓存数量
        /// </summary>
        public int Count
        {
            get{return CacheDict.Count;}
        }

        /// <summary>
        /// 过期模式
        /// </summary>
        public ExpireModeType CurrExpireMode
        {
            get{return ExpireMode;}
            set{ExpireMode = value;}
        }

        /// <summary>
        /// 活动时间
        /// </summary>
        public double Lifetime
        {
            get { return Ttl; }
            set { Ttl = (value < 0.0) ? -1.0 : value; }
        }

        public int SampleRate
        {
            get { return _sampleRate; }
            set { _sampleRate = (value <= 0) ? 1 : value; }
        }

        /// <summary>
        /// 缓存字典值
        /// </summary>
        protected class CacheDictValue
        {
            /// <summary>
            /// 构造
            /// </summary>
            /// <param name="lruLinkNode"></param>
            /// <param name="ageLinkNode"></param>
            public CacheDictValue(LinkedListNode<DataRow> lruLinkNode, LinkedListNode<DataRow> ageLinkNode)
            {
                LruLinkNode = lruLinkNode;
                AgeLinkNode = ageLinkNode;
                CreateTime = DateTime.Now;
                AccessTime = CreateTime;
            }

            /// <summary>
            /// 更新访问时间
            /// </summary>
            public void UpdateAccessTime()
            {
                AccessTime = DateTime.Now;
            }

            /// <summary>
            /// 更新创建时间
            /// </summary>
            public void UpdateCreateTime()
            {
                CreateTime = DateTime.Now;
                AccessTime = CreateTime;
            }

            /// <summary>
            /// 访问时间
            /// </summary>
            public DateTime AccessTime { get; private set; }

            /// <summary>
            /// 
            /// </summary>
            public LinkedListNode<DataRow> AgeLinkNode { get; private set; }

            /// <summary>
            /// 建立时间
            /// </summary>
            public DateTime CreateTime { get; private set; }

            /// <summary>
            /// 
            /// </summary>
            public LinkedListNode<DataRow> LruLinkNode { get; private set; }
        }

    }

    /// <summary>
    /// 自定义事件
    /// </summary>
    public class HasErrorEventArgs : EventArgs
    {
        private readonly Exception ex;
        private readonly int cacheCount;

        /// <summary>
        /// 默认构造
        /// </summary>
        /// <param name="ex">异常</param>
        /// <param name="cacheCount">缓存数量</param>
        public HasErrorEventArgs(Exception ex, int cacheCount)
        {
            this.ex = ex;
            this.cacheCount = cacheCount;
        }

        /// <summary>
        /// 内部异常
        /// </summary>
        public Exception InnerException
        {
            get { return ex; }
        }

        /// <summary>
        /// 缓存数量
        /// </summary>
        public int Caches
        {
            get { return cacheCount; }
        }

    }

    /// <summary>
    /// 辅助类[获取命中率的简单类]
    /// </summary>
    public class SampleQueValue
    {
        public int Hits;
        public bool IsHit;

        public SampleQueValue(int hits, bool isHit)
        {
            Hits = hits;
            IsHit = isHit;
        }
    }

    /// <summary>
    /// 缓存释放与过期方式枚举
    /// </summary>
    public enum ExpireModeType
    {
        /// <summary>
        /// 主动释放
        /// </summary>
        Active,

        /// <summary>
        /// 被动释放
        /// </summary>
        Passive
    }
}